﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CAGFIP.Edicao;
using CAGFIP.Utils;

namespace CAGFIP.Jogo
{
    internal enum PerguntaEndCode
    {
        Acerto,
        Erro,
        Tempo,
        Desistencia,
        ChuteAcerto,
        ChuteErro,
        Pulo
    }

    public enum JogoEndCode
    {
        FimGanhou,
        FimErro,
        FimTempo,
        FimDesistencia,
        Continua
    }

    public delegate void GUIUpdate<T>(T newPontuacaoQtde);

    public class ControladorJogo
    {       
        #region Singleton

        private static ControladorJogo instance;

        private ControladorJogo() { }

        public static ControladorJogo Instance
        {
            get
            {
                if (instance == null)
                    instance = new ControladorJogo();
                return instance;
            }
        }

        #endregion
        
        #region Fields: Gerenciamento Ajudas
   
        private AjudaCollection ajudas;

        #endregion

        #region Fields: Jogo

        private Pontuacao pontuacao;
        private Dificuldade dificuldade;
        private int qtdePerguntasRestantes;
        private List<Pergunta> todasPerguntas;
        private AlternativaCollection alternativasAtual;

        #endregion

        #region GUI Methods: Jogo - Startup

        //retorna lista de assuntos
        public List<Assunto> OnJogoIniciado()
        {
            List<Assunto> assuntos = ControladorEdicao.AssuntoDAO.VisualizarAssuntos();
            if (assuntos.Count == 0) throw Exceptions.ErroAssuntosInexistentes;
         
            return assuntos;
        }

        public void OnConfiguracao(List<int> assuntos, DificuldadeCode dificuldade,
                                    GUIUpdate<int> chuteUpdateHandler,
                                    GUIUpdate<int> puloUpdateHandler,
                                    GUIUpdate<int> remAltUpdateHandler,
                                    GUIUpdate<float> pontuacaoUpdateHandler)
        {
            Dificuldade dif = DificuldadeCollection.Instance[dificuldade];
            
            int qtdePerguntas = ControladorEdicao.PerguntaDAO.BuscarQtdePerguntas(assuntos);
            int qtdePerguntasMin = dif.QtdePerguntas;
            if (qtdePerguntas < qtdePerguntasMin) throw Exceptions.ErroPerguntasInsuficientes;

            this.ajudas = this.ajudas ?? new AjudaCollection(chuteUpdateHandler, puloUpdateHandler, remAltUpdateHandler);
            this.pontuacao = this.pontuacao ?? new Pontuacao(pontuacaoUpdateHandler);

            ajudas.Configurar(dif);
            pontuacao.Configurar(dif);

            this.dificuldade = dif;
            this.todasPerguntas = ControladorEdicao.PerguntaDAO.VisualizarPerguntas(assuntos).Shuffle();
            this.qtdePerguntasRestantes = qtdePerguntasMin;
        }

        #endregion

        #region GUI Methods: Jogo - Rodada

        public JogoEndCode OnAlternativaSelecionada(int indice)
        {
            if (indice == alternativasAtual.GetRight())
                return FinalizarPergunta(PerguntaEndCode.Acerto);
            else
                return FinalizarPergunta(PerguntaEndCode.Erro);
        }

        public JogoEndCode OnChute(int indice)
        {
            this.ajudas[AjudaCode.Chute].Decrementar();

            if (indice == alternativasAtual.GetRight())
                return FinalizarPergunta(PerguntaEndCode.ChuteAcerto);
            else
                return FinalizarPergunta(PerguntaEndCode.ChuteErro);
        }

        public JogoEndCode OnPulo()
        {
            this.ajudas[AjudaCode.Pulo].Decrementar();

            return FinalizarPergunta(PerguntaEndCode.Pulo);
        }

        //retorna indice de botao removido e indice para seleçao automatica de alternativa
        public void OnRemocaoAlternativa(out int idxRemovido, out int idxAutoSelecao)
        {
            this.ajudas[AjudaCode.RemocaoAlternativa].Decrementar();

            idxRemovido = alternativasAtual.GetWrong(out idxAutoSelecao);
        }

        public JogoEndCode OnTempoExpirado()
        {
            FinalizarPergunta(PerguntaEndCode.Tempo);
            return JogoEndCode.FimTempo;
        }

        public JogoEndCode OnDesistenciaConfirmada()
        {
            FinalizarPergunta(PerguntaEndCode.Desistencia);
            return JogoEndCode.FimDesistencia;
        }

        private JogoEndCode FinalizarPergunta(PerguntaEndCode code)
        {
            //atualiza pontuaçao
            this.pontuacao.Atualizar(code);

            //verifica se jogo acaba
            //TODO: implementar: codigo de erros diferentes para telas de termino diferentes
            switch (code)
            {
                case PerguntaEndCode.Acerto:
                case PerguntaEndCode.ChuteAcerto:
                case PerguntaEndCode.ChuteErro:
                case PerguntaEndCode.Pulo:
                    if (--this.qtdePerguntasRestantes == 0)
                        return JogoEndCode.FimGanhou;
                    else
                        return JogoEndCode.Continua;
                case PerguntaEndCode.Erro:
                    return JogoEndCode.FimErro;
                case PerguntaEndCode.Tempo:
                    return JogoEndCode.FimTempo;
                case PerguntaEndCode.Desistencia:
                    return JogoEndCode.FimDesistencia;
                default:
                    throw new NotImplementedException();
            }
        }

        #endregion

        #region GUI Methods: Jogo - Obtenção da pergunta

        public void GetPergunta(out string enunciado, out AlternativaCollection alternativas, out int tempo)
        {
            var pergunta = this.todasPerguntas[this.qtdePerguntasRestantes - 1]; //pergunta é sempre obtida de traz pra frente... eficiencia
            this.alternativasAtual = alternativas = new AlternativaCollection(pergunta.AlternativaCorreta, pergunta.AlterntivasIncorretas).Shuffle();
            enunciado = pergunta.Enunciado;
            tempo = this.dificuldade.TempoResposta;
        }

        #endregion

        #region Methods: Fim do jogo - Ranking

        //retorna necessidade de cadastro
        public bool FinalizarJogo()
        {
            List<Ranking> rankingList = ControladorRanking.RankingDAO.VisualizarRanking();
            foreach (Ranking ranking in rankingList)
                if (this.dificuldade.Nome.Equals(ranking.Dificuldade, StringComparison.Ordinal) && this.pontuacao.Pontos > ranking.Pontuacao)
                    return true;
            return false;
        }

        public void GetEndGameInfo(out string dificuldade, out float pontuacao)
        {
            dificuldade = this.dificuldade.Nome;
            pontuacao = this.pontuacao.Pontos;
        }

        #endregion
    }
}
